package ace.module.stock.core.impl.manager.impl;

import ace.cmp.core.constants.CoreConstant;
import ace.cmp.core.utils.SystemClockUtils;
import ace.cmp.data.spring.data.jdbc.querydsl.rsql.core.model.SaveContext;
import ace.cmp.data.spring.data.jdbc.querydsl.rsql.core.utils.SaveContextUtils;
import ace.cmp.util.AceAssertUtils;
import ace.cmp.util.AceStreamUtils;
import ace.module.common.core.impl.manager.impl.AbstractEntityOfAppManager;
import ace.module.stock.api.enums.WmsSalesQuantityObjectTypeEnum;
import ace.module.stock.api.enums.WmsStockObjectTypeEnum;
import ace.module.stock.api.model.dto.WmsSalesQuantityDto;
import ace.module.stock.api.model.input.WmsBizInitInput;
import ace.module.stock.api.model.input.WmsBizInitObjectInput;
import ace.module.stock.api.model.input.WmsBizParentIdInput;
import ace.module.stock.api.model.input.WmsBizUniqueIdInput;
import ace.module.stock.core.impl.dao.aggregate.helper.WmsSalesQuantityAggregateHelper;
import ace.module.stock.core.impl.dao.entity.QWmsSalesQuantity;
import ace.module.stock.core.impl.dao.entity.WmsApplication;
import ace.module.stock.core.impl.dao.entity.WmsSalesQuantity;
import ace.module.stock.core.impl.dao.model.bo.WmsSalesQuantityDetailSaveContextBo;
import ace.module.stock.core.impl.dao.repository.WmsSalesQuantityRepository;
import ace.module.stock.core.impl.manager.WmsSalesQuantityManager;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Ops;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.Expressions;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * 销售数量 spring data jdbc querydsl rsql manager implement.
 *
 * @author Caspar
 */
@Slf4j
@AllArgsConstructor
@Component
public class WmsSalesQuantityManagerImpl
    extends AbstractEntityOfAppManager<WmsSalesQuantity, Long, WmsSalesQuantityDto>
    implements WmsSalesQuantityManager {

  private final WmsSalesQuantityRepository wmsSalesQuantityRepository;
  private final WmsSalesQuantityAggregateHelper wmsSalesQuantityAggregateHelper;

  @Override
  public WmsSalesQuantityDetailSaveContextBo assembleInitOrNoneSaveListContext(WmsApplication wmsApplication, WmsBizInitInput wmsBizInitInput) {
    Long wmsAppId = wmsApplication.getId();
    List<Long> skuObjectIds = wmsBizInitInput.getObjects()
        .stream()
        .map(WmsBizInitObjectInput::getObjectId)
        .collect(Collectors.toList());
    Long productObjectId = wmsBizInitInput.getObjectParentId();

    List<WmsSalesQuantity> allOldWmsSalesQuantities = this.getProductAndSku(wmsAppId, productObjectId, skuObjectIds);

    List<SaveContext<WmsSalesQuantity>> skuWmsSalesQuantitySaveContexts = wmsBizInitInput
        .getObjects()
        .stream()
        .map(wmsStockAndSalesQuantityInitInput -> {
          return this.assembleInitOrNoneSaveListContext(
              wmsApplication.getId(),
              allOldWmsSalesQuantities,
              WmsSalesQuantityObjectTypeEnum.PRODUCT_SKU,
              wmsBizInitInput.getObjectParentId(),
              wmsStockAndSalesQuantityInitInput.getObjectId(),
              wmsStockAndSalesQuantityInitInput.getQuantity()
          );
        })
        .collect(Collectors.toList());

    Long totalQuantity = skuWmsSalesQuantitySaveContexts.stream()
        .map(SaveContext::getData)
        .map(WmsSalesQuantity::getQuantity)
        .mapToLong(Long::longValue)
        .sum();

    SaveContext<WmsSalesQuantity> productWmsStockSaveContext = this.assembleInitOrNoneSaveListContext(
        wmsApplication.getId(),
        allOldWmsSalesQuantities,
        WmsSalesQuantityObjectTypeEnum.PRODUCT,
        CoreConstant.ZERO_LONG,
        wmsBizInitInput.getObjectParentId(),
        totalQuantity
    );

    return WmsSalesQuantityDetailSaveContextBo.builder()
        .children(SaveContextUtils.of(skuWmsSalesQuantitySaveContexts))
        .salesQuantity(productWmsStockSaveContext)
        .build();

  }

  private SaveContext<WmsSalesQuantity> assembleInitOrNoneSaveListContext(
      Long wmsAppId,
      List<WmsSalesQuantity> allOldWmsSalesQuantities,
      WmsSalesQuantityObjectTypeEnum objectTypeEnum,
      Long objectParentId,
      Long objectId,
      Long quantity) {
    WmsSalesQuantity wmsSalesQuantity = this.wmsSalesQuantityAggregateHelper
        .findByObjectIdAndObjectType(allOldWmsSalesQuantities, objectId, objectTypeEnum.getCode());

    boolean isAdd = Objects.isNull(wmsSalesQuantity);

    if (isAdd) {
      wmsSalesQuantity = WmsSalesQuantity.builder()
          .id(this.getNewId())
          .wmsAppId(wmsAppId)
          .objectId(objectId)
          .objectParentId(objectParentId)
          .objectType(objectTypeEnum.getCode())
          .quantity(quantity)

          .deleteFlag(CoreConstant.DELETE_FLAG_NOT)
          .createTime(SystemClockUtils.currentTimeMillis())
          .updateTime(SystemClockUtils.currentTimeMillis())
          .rowVersion(CoreConstant.DEFAULT_ROW_VERSION)
          .build();
      return SaveContextUtils.of(wmsSalesQuantity, isAdd);
    } else {
      return SaveContextUtils.<WmsSalesQuantity>empty();
    }
  }

  private List<WmsSalesQuantity> getProductAndSku(Long wmsAppId, Long productObjectId, List<Long> skuObjectIds) {
    List<WmsSalesQuantity> wmsSalesQuantities = this.wmsSalesQuantityRepository.query(query -> {
      QWmsSalesQuantity qWmsSalesQuantity = QWmsSalesQuantity.wmsSalesQuantity;
      return query.select(qWmsSalesQuantity)
          .from(qWmsSalesQuantity)
          .where(qWmsSalesQuantity.wmsAppId.eq(wmsAppId))
          .where(
              Expressions.predicate(
                  Ops.WRAPPED,
                  qWmsSalesQuantity.objectId.eq(productObjectId),
                  qWmsSalesQuantity.objectType.eq(WmsSalesQuantityObjectTypeEnum.PRODUCT.getCode())
              )
          )
          .where(
              Expressions.predicate(
                  Ops.WRAPPED,
                  qWmsSalesQuantity.objectId.in(skuObjectIds),
                  qWmsSalesQuantity.objectType.eq(WmsSalesQuantityObjectTypeEnum.PRODUCT_SKU.getCode())
              )
          )
          .where(qWmsSalesQuantity.deleteFlag.eq(CoreConstant.DELETE_FLAG_NOT))
          .fetch();
    });
    return wmsSalesQuantities;
  }

  @Transactional(rollbackFor = Throwable.class)
  @Override
  public void initOrNone(WmsApplication wmsApplication, WmsBizInitInput input) {
    WmsSalesQuantityDetailSaveContextBo wmsSalesQuantityDetailSaveContextBo = this.assembleInitOrNoneSaveListContext(wmsApplication, input);
    List<WmsSalesQuantity> newWmsSalesQuantities = wmsSalesQuantityDetailSaveContextBo
        .getSaveListContext()
        .getNewOriginData();

    if (CollectionUtils.isNotEmpty(newWmsSalesQuantities)) {
      this.addIgnore(newWmsSalesQuantities, false);
    }
  }

  @Override
  public List<WmsSalesQuantity> getSkuAndAllRecursionParentsByWmsAppIdAndObjectIdIn(Long wmsAppId, List<Long> objectIds) {
    if (CollectionUtils.isEmpty(objectIds) || Objects.isNull(wmsAppId)) {
      return new ArrayList<>();
    }
    Integer objectType = WmsStockObjectTypeEnum.PRODUCT_SKU.getCode();

    List<WmsSalesQuantity> startWmsSalesQuantities = this.wmsSalesQuantityRepository.findByWmsAppIdAndObjectIdInAndObjectTypeAndDeleteFlag(wmsAppId, objectIds, objectType,
        CoreConstant.DELETE_FLAG_NOT);

    return this.getAndConcatAllRecursionParentStocks(startWmsSalesQuantities);
  }


  @Override
  public List<WmsSalesQuantity> getListByWmsAppIdAndBizParentId(Long wmsAppId, WmsBizParentIdInput wmsBizParentIdInput) {
    QWmsSalesQuantity qWmsSalesQuantity = QWmsSalesQuantity.wmsSalesQuantity;
    Predicate predicate = ExpressionUtils.allOf(
        qWmsSalesQuantity.wmsAppId.eq(wmsAppId),
        qWmsSalesQuantity.objectParentId.eq(wmsBizParentIdInput.getObjectParentId()),
        qWmsSalesQuantity.objectType.eq(wmsBizParentIdInput.getObjectType()),
        qWmsSalesQuantity.deleteFlag.eq(CoreConstant.DELETE_FLAG_NOT)
    );
    return this.wmsSalesQuantityRepository.findAll(predicate);
  }

  @Override
  public List<WmsSalesQuantityDto> getDtoListByWmsAppIdAndBizParentId(Long wmsAppId, WmsBizParentIdInput wmsBizParentIdInput) {
    List<WmsSalesQuantity> wmsSalesQuantities = this.getListByWmsAppIdAndBizParentId(wmsAppId, wmsBizParentIdInput);
    return this.convertToEntityDto(wmsSalesQuantities);
  }

  @Override
  public void checkNotContainsAllByObjectId(List<WmsSalesQuantity> skuWmsSalesQuantities, List<Long> skuObjectIdInputs) {
    AceAssertUtils.isNotNull(skuWmsSalesQuantities.isEmpty(), "sku销量不存在");

    List<Long> existObjectIds = skuWmsSalesQuantities.stream()
        .map(WmsSalesQuantity::getObjectId)
        .collect(Collectors.toList());

    Long notExistObjectId = skuObjectIdInputs.stream()
        .filter(objectId -> !existObjectIds.contains(objectId))
        .findFirst()
        .orElse(null);

    AceAssertUtils.isNotNull(notExistObjectId, "[%s]sku销量不存在", notExistObjectId);
  }

  @Override
  public List<WmsSalesQuantity> getListByWmsAppIdAndBizUniqueIdIn(Long wmsAppId, List<WmsBizUniqueIdInput> bizUniqueIdInputs) {
    QWmsSalesQuantity qWmsSalesQuantity = QWmsSalesQuantity.wmsSalesQuantity;
    List<Predicate> bizUniqueIdPredicates = bizUniqueIdInputs.stream()
        .map(bizUniqueIdInput -> {
          return ExpressionUtils.allOf(
              qWmsSalesQuantity.objectId.eq(bizUniqueIdInput.getObjectId()),
              qWmsSalesQuantity.objectType.eq(bizUniqueIdInput.getObjectType())
          );
        })
        .collect(Collectors.toList());
    Predicate predicate = ExpressionUtils.allOf(
        qWmsSalesQuantity.wmsAppId.eq(wmsAppId),
        ExpressionUtils.anyOf(bizUniqueIdPredicates),
        qWmsSalesQuantity.deleteFlag.eq(CoreConstant.DELETE_FLAG_NOT)
    );
    return this.wmsSalesQuantityRepository.findAll(predicate);
  }

  @Override
  public List<WmsSalesQuantityDto> getDtoListByWmsAppIdAndBizUniqueIdIn(Long wmsAppId, List<WmsBizUniqueIdInput> bizUniqueIdInputs) {
    List<WmsSalesQuantity> wmsSalesQuantities = this.getListByWmsAppIdAndBizUniqueIdIn(wmsAppId, bizUniqueIdInputs);
    return this.convertToEntityDto(wmsSalesQuantities);
  }

  @Override
  public WmsSalesQuantityDto findDtoByBizUniqueId(List<WmsSalesQuantityDto> wmsSalesQuantityDtos, WmsBizUniqueIdInput bizUniqueIdInput) {
    return AceStreamUtils.ofNullable(wmsSalesQuantityDtos)
        .filter(p -> Objects.nonNull(p.getObjectId()) && Objects.nonNull(p.getObjectType()))
        .filter(p -> p.getObjectId().longValue() == bizUniqueIdInput.getObjectId().longValue())
        .filter(p -> p.getObjectType().intValue() == bizUniqueIdInput.getObjectType().intValue())
        .findFirst()
        .orElse(null);
  }

  private List<WmsSalesQuantity> getAndConcatAllRecursionParentStocks(List<WmsSalesQuantity> startWmsSalesQuantities) {
    if (CollectionUtils.isEmpty(startWmsSalesQuantities)) {
      return new ArrayList<>();
    }

    List<WmsSalesQuantity> result = new ArrayList<>(startWmsSalesQuantities);

    List<WmsSalesQuantity> allParentWmsStocks = this.getAllRecursionParentStocks(startWmsSalesQuantities);

    result.addAll(allParentWmsStocks);

    return result;
  }

  private List<WmsSalesQuantity> getAllRecursionParentStocks(List<WmsSalesQuantity> wmsSalesQuantities) {
    if (CollectionUtils.isEmpty(wmsSalesQuantities)) {
      return new ArrayList<>();
    }

    List<WmsSalesQuantity> result = new ArrayList<>();

    Set<Long> parentIds = this.getParentIds(wmsSalesQuantities);

    while (CollectionUtils.isNotEmpty(parentIds)) {
      List<WmsSalesQuantity> parentWmsStocks = this.wmsSalesQuantityRepository.findByIdInAndDeleteFlagIsNot(parentIds);
      if (CollectionUtils.isNotEmpty(parentWmsStocks)) {
        result.addAll(parentWmsStocks);
      }
      parentIds = this.getParentIds(parentWmsStocks);
    }

    return result;
  }

  private Set<Long> getParentIds(Collection<WmsSalesQuantity> wmsSalesQuantities) {
    return wmsSalesQuantities.stream()
        .map(WmsSalesQuantity::getParentId)
        .filter(parentId -> parentId != null && parentId > 0)
        .collect(Collectors.toSet());
  }
}
